/**
 * 
 */
package edu.arizona.bio5.onto.decomposition.persistent.rdf;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;

import edu.arizona.bio5.onto.decomposition.Atom;
import edu.arizona.bio5.onto.decomposition.AtomicDecomposition;
import edu.arizona.bio5.onto.decomposition.Label;
import edu.arizona.bio5.onto.decomposition.MSSLabel;
import edu.arizona.bio5.onto.decomposition.SignatureLabel;
import edu.arizona.bio5.onto.decomposition.persistent.ADPersistenceException;
import edu.arizona.bio5.onto.decomposition.persistent.ADPersister;
import edu.arizona.bio5.onto.decomposition.persistent.LabelPersister;

/**
 * Persists AD into a Sesame repository
 * 
 * @author Pavel Klinov
 *
 */
public class SesameADPersister implements ADPersister {

	private RepositoryConnection mConnection = null;
	private ValueFactory mValueFactory = null;
	private Map<String, LabelPersister> mLabelPersisters = new HashMap<String, LabelPersister>();
	
	public SesameADPersister(final RepositoryConnection theConn) {
		mConnection = theConn;
		mValueFactory = theConn.getValueFactory();
		mLabelPersisters.put(MSSLabel.TYPE, new SesameMSSLabelPersister(mConnection));
		mLabelPersisters.put(SignatureLabel.TYPE, new SesameSignatureLabelPersister(mConnection));
	}
	
	
	@Override
	public void persist(AtomicDecomposition ad) throws ADPersistenceException {
		for (LabelPersister labelPersister : mLabelPersisters.values()) {
			((SesameAbstractPersister)labelPersister).setNamedGraph(getContext(ad));
		}

		try {
			mConnection.setAutoCommit(false);
			
			//start adding some data
			storeADInfo(ad);
			System.out.print("Storing the AD graph structure...");
			storeADGraph(ad);
			System.out.println("d one!");
			System.out.print("Storing labels...");
			storeLabels(ad);
			System.out.println("done!");
			System.out.print("Storing term index...");
			storeTermIndex(ad);
			System.out.println("done!");
			System.out.print("Storing atoms...");
			storeAtoms(ad);
			storeGlobalAxioms(ad);
			System.out.println("done!");
			
			commit();
			
		} catch (RepositoryException e) {
			//Something went wrong when storing
			throw new ADPersistenceException(e);
		} finally {
			try {
				if (mConnection != null) {
					mConnection.close();
				}
			} catch (RepositoryException e) {}
		}
	}

	protected void storeGlobalAxioms(AtomicDecomposition ad) throws ADPersistenceException {
		URI context = mValueFactory.createURI(RDFADConstants.AD_BASE_NS + ad.getOntologyURI().toString() + "/" + RDFADConstants.AD_GLOBAL_AXIOMS_GRAPH);
		
		persistAxioms(ad.getGlobalAxioms(), OWLManager.createOWLOntologyManager(), context);
	}


	protected void storeAtoms(AtomicDecomposition ad) throws ADPersistenceException {
		OWLOntologyManager mgr = OWLManager.createOWLOntologyManager();
	
		for (Atom atom : ad.getAtoms()) {
			//If multiple ADs are stored in a single RDF DB, we'll need to create context URIs w/ a common prefix
			persistAxioms(atom.getAllAxioms(), mgr, SesameOWLRDFUtils.getAtomURI(atom, mValueFactory));
		}
	}

	protected void persistAxioms(Set<OWLAxiom> axioms, OWLOntologyManager manager, URI atomContext) throws ADPersistenceException {
		OWLOntology ontology = null;
		OWLDataFactory factory = manager.getOWLDataFactory();
		Set<OWLAxiom> addedDecls = new HashSet<OWLAxiom>();
		
		try {
			ontology = manager.createOntology(axioms);
			
			for (OWLEntity term : ontology.getSignature()) {
				//Declare all entities to avoid ambiguities when loading back later
				addedDecls.add(factory.getOWLDeclarationAxiom(term));
			}
			
			manager.applyChanges(manager.addAxioms(ontology, addedDecls));			
		} 
		catch (OWLOntologyCreationException e) {
			throw new ADPersistenceException(e);
		}
		
		OWLAPIToSesameTranslator visitor = new OWLAPIToSesameTranslator(manager, ontology, mConnection);
		
		visitor.setContext(atomContext);
		ontology.accept(visitor);
		
		if (visitor.getError() != null) {
			throw new ADPersistenceException(visitor.getError());
		}
	}

	protected void storeTermIndex(AtomicDecomposition ad) throws ADPersistenceException {
		if (ad.getTermBasedIndex() == null) return;
		
		URI context = getContext(ad);
		URI hasRelevant = mValueFactory.createURI(RDFADConstants.AD_TERM_HAS_RELEVANT_ATOMS);
		URI containsAtom = mValueFactory.createURI(RDFADConstants.AD_CONTAINS_ATOM);
		int relAtomsIndex = 0;
		
		try {
			for (java.net.URI term : ad.getTermBasedIndex().keySet()) {
				URI relevantAtoms = mValueFactory.createURI(RDFADConstants.AD_ATOM_CONTAINER_INSTANCE + (relAtomsIndex++));
				
				mConnection.add(mValueFactory.createURI(term.toString()), hasRelevant, relevantAtoms, context);
				
				for (Atom atom : ad.getTermBasedIndex().get(term)) {
					mConnection.add(relevantAtoms, containsAtom, SesameOWLRDFUtils.getAtomURI(atom, mValueFactory), context);
				}
			}
		} catch (RepositoryException e) {
			throw new ADPersistenceException(e);
		}
	}


	protected void storeLabels(AtomicDecomposition ad) throws ADPersistenceException {
		for (Atom atom : ad.getAtoms()) {
			for (Label label : atom.getLabels()) {
				if (mLabelPersisters.containsKey(label.getType())) {
					mLabelPersisters.get(label.getType()).persist(atom);
				}
			}
		}
	}

	private void storeADGraph(AtomicDecomposition ad) throws RepositoryException {
		URI context = getContext(ad);
		URI atomClass = mValueFactory.createURI(RDFADConstants.AD_ATOM_TYPE);
		URI dependsOn = mValueFactory.createURI(RDFADConstants.AD_ATOM_DEPENDS_ON);
		
		for (Atom atom : ad.getAtoms()) {
			//first: :a rdf:type ad:Atom
			URI atomURI = SesameOWLRDFUtils.getAtomURI(atom, mValueFactory);
			
			mConnection.add(atomURI, RDF.TYPE, atomClass, context);
			//second, dependencies: :a ad:dependsOn :b
			for (Atom dep : ad.getDependencies(atom, true)) {
				mConnection.add(atomURI, dependsOn, SesameOWLRDFUtils.getAtomURI(dep, mValueFactory), context);
			}
		}
	}

	protected void commit() throws RepositoryException {
		mConnection.commit();
	}

	protected void storeADInfo(AtomicDecomposition ad) throws RepositoryException {
		URI context = getContext(ad);
		URI hasModType = mValueFactory.createURI(RDFADConstants.AD_HAS_MODULE_TYPE);
		Literal modType = mValueFactory.createLiteral(ad.getModuleType().name());
		
		mConnection.add(context, hasModType, modType, context);
	}

	protected URI getContext(AtomicDecomposition ad) {
		return mValueFactory.createURI(RDFADConstants.AD_BASE_NS + ad.getOntologyURI().toString());
	}
}